package com.fezs.redis.aop;

import com.fezs.common.constants.CacheConstant;
import com.fezs.common.exception.BusinessException;
import com.fezs.common.utils.SpelUtils;
import com.fezs.redis.annotation.RedissonLock;
import com.fezs.redis.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.core.annotation.Order;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author zhukai
 * @since 2022/8/18
 */
@Slf4j
@Aspect
@Order(Integer.MIN_VALUE)
public class RedissonLockAop {

    @Around("@annotation(lockAnno)")
    public Object around(ProceedingJoinPoint joinPoint, RedissonLock lockAnno) throws Throwable {
        String[] keys = lockAnno.keys();
        long waitTime = lockAnno.waitTime();
        long expire = lockAnno.expire();
        String msg = lockAnno.msg();

        // 解析spel表达式获取key
        String key = this.getCombineKey(keys, joinPoint);

        RLock lock = RedisUtils.getClient().getLock(key);
        try {
            boolean flag = lock.tryLock(waitTime, expire, TimeUnit.MILLISECONDS);
            if (!flag) {
                log.warn("Key[{}]没有获取到锁", key);
                throw new BusinessException(msg);
            }
            return joinPoint.proceed();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public String getCombineKey(String[] spelExpression, ProceedingJoinPoint joinPoint) {
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Object[] args = joinPoint.getArgs();

        // 前缀+方法全限定名+自定义字段
        StringBuilder sb = new StringBuilder(CacheConstant.LOCK_KEY_PREFIX);
        sb.append(":");
        sb.append(method.getDeclaringClass().getSimpleName()).append(".").append(method.getName());
        sb.append("#");
        if (spelExpression != null && (spelExpression.length > 1 || !"".equals(spelExpression[0]))) {
            sb.append(SpelUtils.getSpelDefinitionKey(method, spelExpression, args));
        }
        return sb.toString();
    }

}
